package validator

import (
	"bytes"
	"encoding/json"
	"errors"
	lvdberror "github.com/syndtr/goleveldb/leveldb/errors"
	"github.com/wanchain/go-mpc/common"
	"github.com/wanchain/go-mpc/common/hexutil"
	"github.com/wanchain/go-mpc/crypto"
	"github.com/wanchain/go-mpc/log"
	mpcprotocol "github.com/wanchain/go-mpc/storeman/storemanmpc/protocol"
	"time"
)

type TxHandler interface {
	GetHashedData() [][]byte
	ValidateTx() bool
	GetKey() []byte
}

func inByteArray(data *[]byte, collection *[][]byte) bool {
	if data == nil || len(*collection) == 0 {
		return false
	}

	for _, value := range *collection {
		if bytes.Compare(*data, value) == 0 {
			return true
		}
	}
	return false
}

func deleteInByteArray(data *[]byte, collection *[][]byte) [][]byte {
	if !inByteArray(data, collection) {
		return *collection
	}

	ret := make([][]byte, 0)
	for _, value := range *collection {
		if bytes.Compare(*data, value) != 0 {
			ret = append(ret, value)
		}
	}
	return ret
}

func waitKeyFromDB(keys [][]byte) ([]byte, error) {
	log.SyslogInfo("waitKeyFromDB, begin")

	for i, key := range keys {
		log.SyslogInfo("waitKeyFromDB", "i", i, "key", hexutil.Encode(key))
	}

	db, err := GetDB()
	if err != nil {
		log.SyslogErr("waitKeyFromDB get database fail", "err", err.Error())
		return nil, err
	}

	start := time.Now()
	for {
		for _, key := range keys {
			isExist, err := db.Has(key)
			if err != nil {
				log.SyslogErr("================= waitKeyFromDB fail", "err", err.Error())
				return nil, err
			} else if isExist {
				log.SyslogInfo("================= waitKeyFromDB, got it", "key", common.ToHex(key))
				return key, nil
			} else {
				// comment because of too many logs.
				//log.SyslogInfo("================= waitKeyFromDB, not exist", "key", common.ToHex(key))
			}

		}

		if time.Now().Sub(start) >= mpcprotocol.MPCTimeOutValidate {
			log.SyslogInfo("waitKeyFromDB, time out")
			return nil, errors.New("waitKeyFromDB, time out")
		}

		time.Sleep(200 * time.Microsecond)
	}

	return nil, errors.New("waitKeyFromDB, unknown error")
}

func addKeyValueToDB(key, value []byte) error {
	log.SyslogInfo("addKeyValueToDB, begin", "key", common.ToHex(key))
	sdb, err := GetDB()
	if err != nil {
		log.SyslogErr("addKeyValueToDB, getting storeman database fail", "err", err.Error())
		return err
	}

	err = sdb.Put(key, value)
	if err != nil {
		log.SyslogErr("addKeyValueToDB, getting storeman database fail", "err", err.Error())
		return err
	}

	ret, err := sdb.Get(key)
	if err != nil {
		log.SyslogErr("addKeyValueToDB, getting storeman database fail", "err", err.Error())
		return err
	}

	log.SyslogInfo("addKeyValueToDB succeed to get data from level db after putting key-val pair", "ret", string(ret))
	return nil
}

func deleteKeyValueFromDb(key []byte) error {
	log.SyslogInfo("deleteKeyValueFromDb, begin", "key", common.ToHex(key))
	sdb, err := GetDB()
	if err != nil {
		log.SyslogErr("deleteKeyValueFromDb, getting storeman database fail", "err", err.Error())
		return err
	}

	err = sdb.Delete(key)
	if err != nil {
		log.SyslogErr("addKeyValueToDB, getting storeman database fail", "err", err.Error())
		return err
	}

	log.SyslogInfo("deleteKeyValueFromDb succeed from db", "key", common.ToHex(key))
	return nil
}

func buildKeyFromBytes(keyBytes []byte, status string) []byte {
	// build the key.
	var buffer bytes.Buffer
	buffer.Write(keyBytes[:])
	buffer.Write([]byte(status))
	return crypto.Keccak256(buffer.Bytes())
}

func addApproving(approvedKey, approvingKey, approvingKeysKey, value []byte) error {
	sdb, err := GetDB()
	if err != nil {
		log.SyslogErr("addApproving, getting storeman database fail", "err", err.Error())
		return err
	}

	// check in approved db
	isExist, err := sdb.Has(approvedKey)
	if err != nil {
		log.SyslogErr("addApproving", "sdb.Has err", err.Error())
		return err
	}
	if isExist {
		log.SyslogInfo("addApproving", "isExist in approvedDB", "true")
		return nil
	} else {
		log.SyslogInfo("addApproving", "isExist in approvedDB", "false")
	}
	// check in approving keys
	sdb.Lock()
	defer sdb.UnLock()
	ret, err := sdb.Get([]byte(approvingKeysKey)) // todo lock?
	if err != nil && err != lvdberror.ErrNotFound {
		log.SyslogErr("addApproving,  sdb.Get fail", "err", err.Error())
		return err
	}
	var approvingKeys [][]byte
	if len(ret) != 0 {
		err = json.Unmarshal(ret, &approvingKeys)
		if err != nil {
			log.SyslogErr("addApproving,  Unmarshal fail", "err", err.Error())
			return err
		}
	}
	// in approvingKey or not
	inApprovingKeys := inByteArray(&approvingKey, &approvingKeys)
	// check in approving db
	existApprovingDb, err := sdb.Has(approvingKey)
	if err != nil {
		log.SyslogErr("addApproving, sdb.Has fail", "err", err.Error())
		return err
	}
	log.SyslogErr("addApproving ",
		"existApprovingDb", existApprovingDb,
		"inApprovingKeys", inApprovingKeys)

	if existApprovingDb {
		// delete for update the RcvTimeStamp
		err = deleteKeyValueFromDb(approvingKey)
		if err != nil {
			log.SyslogErr("addApproving deleteKeyValueFromDb", "err", err.Error())
			return err
		}
	}

	// put in approving db
	err = addKeyValueToDB(approvingKey, value)
	log.SyslogInfo("addApproving[not existApprovingDb] ", "approvingKey", hexutil.Encode(approvingKey), "value", value)
	if err != nil {
		log.SyslogErr("addApproving, addKeyValueToDB fail", "err", err.Error())
		return err
	}

	// append key in keys
	if !inApprovingKeys {
		approvingKeys = append(approvingKeys, approvingKey)

		approvingKeysBytes, err := json.Marshal(&approvingKeys)

		for i := 0; i < len(approvingKeys); i++ {
			log.SyslogInfo("addApproving, Marshal approvingKeys", "i", i, "approving key", hexutil.Encode(approvingKeys[i]))
		}
		if err != nil {
			log.SyslogErr("addApproving, Marshal approvingKeys fail", "err", err.Error())
			return err
		}

		log.SyslogInfo("addApproving ",
			"approvingKeysKey", hexutil.Encode(approvingKeysKey),
			"approvingKeysKey[str]", string(approvingKeysKey),
			"value", hexutil.Encode(approvingKeysBytes))
		err = addKeyValueToDB(approvingKeysKey, approvingKeysBytes) // todo lock
		if err != nil {
			log.SyslogErr("addApproving,  MpcApprovingKeys addKeyValueToDB fail", "err", err.Error())
			return err
		}
	}

	return nil
}

func approve(approvedKey, approvingKey, approvingKeysKey, value []byte) error {

	sdb, err := GetDB()
	if err != nil {
		log.SyslogErr("approve, getting storeman database fail", "err", err.Error())
		return err
	}
	sdb.Lock()
	defer sdb.UnLock()
	ret, err := sdb.Get([]byte(approvingKeysKey)) //todo lock
	if err != nil {
		log.SyslogErr("approve, getting storeman database fail", "err", err.Error())
		return err
	}
	var approvingKeys [][]byte
	err = json.Unmarshal(ret, &approvingKeys)
	if err != nil {
		log.SyslogErr("approve, Unmarshal fail", "err", err.Error())
		return err
	}
	// check in approving keys
	if !inByteArray(&approvingKey, &approvingKeys) {
		log.SyslogErr("approve, not in approving keys")
		return err
	}
	// in approving keys
	// check in approving db
	exist, err := sdb.Has(approvingKey)
	if !exist {
		log.SyslogErr("approve, not in approving keys")
		return errors.New("can not fond in approving db")
	}
	if err != nil {
		log.SyslogErr("approve, sdb.Has error", "err:", err.Error())
		return err
	}
	// in approving db
	// add in approved DB

	err = addKeyValueToDB(approvedKey, value)
	if err != nil {
		log.SyslogErr("approve, addKeyValueToDB fail", "err", err.Error())
		return err
	}
	// in approving keys
	// delete key in keys
	newApprovingKeys := deleteInByteArray(&approvingKey, &approvingKeys)
	newApprovingKeysBytes, err := json.Marshal(&newApprovingKeys)
	if err != nil {
		log.SyslogErr("approve, Marshal fail", "err", err.Error())
		return err
	}
	err = addKeyValueToDB([]byte(approvingKeysKey), newApprovingKeysBytes) //todo lock
	if err != nil {
		log.SyslogErr("approve, addKeyValueToDB MpcApprovingKeys fail", "err", err.Error())
		return err
	}
	// in approving db
	// delete in approving db	err = sdb.Delete(approvingKey)
	if err != nil {
		log.SyslogErr("approve, Delete in approving db", "err", err.Error())
		return err
	}
	return nil
}

func getForApprove(approvingKeysKey []byte) ([][]byte, error) {
	log.SyslogInfo("getForApprove, begin", "[approvingKeysKey]", string(approvingKeysKey))
	sdb, err := GetDB()
	if err != nil {
		log.SyslogErr("getForApprove, getting storeman database fail [GetDB()]", "err", err.Error())
		return nil, nil
	}

	log.SyslogInfo("getForApprove", "key", approvingKeysKey)
	sdb.Lock()
	defer sdb.UnLock()
	ret, err := sdb.Get(approvingKeysKey) //todo lock
	if err != nil {
		log.SyslogErr("getForApprove, getting storeman database fail [approvingKeysKey]", "err", err.Error())
		return nil, nil
	}
	var approvingKeys [][]byte
	err = json.Unmarshal(ret, &approvingKeys)
	if err != nil {
		log.SyslogErr("getForApprove, Unmarshal", "err", err.Error())
		return nil, nil
	}
	var approving [][]byte
	for i := 0; i < len(approvingKeys); i++ {
		approvingItem, err := sdb.Get(approvingKeys[i])
		if err != nil {
			log.SyslogErr("getForApprove, getting storeman database fail [approvingKeys[i]]", "err", err.Error())
			//return nil, err
			continue
		}

		approving = append(approving, approvingItem)
	}
	return approving, nil
}
